﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using _3DTicTacToe._3D;

namespace _3DTicTacToe.UI.Components
{
    partial class GameView3D : UserControl
    {
        bool _mouseDown;
        Point _lastMousePosition;
        ModelVisual3D _previewModel;

        public GameView3D()
        {
            InitializeComponent();

            model.Transform = new Transform3DGroup();
        }

        #region Property Accessors

        public Color BackgroundColor 
        {
            get { return (backgroundGrid.Background as SolidColorBrush).Color; }
            set { backgroundGrid.Background = value.CreateBrush(); }
        }

        #endregion

        #region Public Methods

        public void UpdatePreview(Point3 point, Color color, bool isPlayerOne)
        {
            ClearPreview();

            var material = CreateMaterial(color, 0.5);
            var pos = Compute3DPositions(point.X1, point.X2, point.X3);

            _previewModel = (isPlayerOne) ? new Cube(35, pos) { Material = material } as ModelVisual3D :
                                            new Ball(40, pos) { Material = material } as ModelVisual3D;
            model.Children.Add(_previewModel);
        }

        public void UpdateGameView(Point3 point, Color color, bool isPlayerOne)
        {
            ClearPreview();

            var material = CreateMaterial(color, 0.8);
            var pos = Compute3DPositions(point.X1, point.X2, point.X3);

            var obj = (isPlayerOne) ? new Cube(35, pos) { Material = material } as ModelVisual3D :
                                      new Ball(40, pos) { Material = material } as ModelVisual3D;
            model.Children.Add(obj);
        }

        public void ClearPreview()
        {
            model.Children.Remove(_previewModel);
        }

        public void ClearField()
        {
            model.Children.Clear();
        }

        public void ResetCameraRotation()
        {
            model.Transform = new Transform3DGroup();
        }

        public void ResetCameraZoom()
        {
            camera.Position = new Point3D(0, -260, 0);
        }

        #endregion

        #region Helper Functions

        private static Material CreateMaterial(Color color, double opacity)
        {
            var dbrush = new SolidColorBrush(color) { Opacity = opacity };
            var sbrush = new SolidColorBrush(Colors.Silver) { Opacity = opacity };
            var dmaterial = new DiffuseMaterial(dbrush);
            var smaterial = new SpecularMaterial(sbrush, 5);
            var material = new MaterialGroup();
            material.Children.Add(dmaterial);
            material.Children.Add(smaterial);

            return material;
        }

        private static Point3D Compute3DPositions(int x1, int x2, int x3)
        {
            var x = (x1 == 0) ? -75 : ((x1 == 1) ? -25 : ((x1 == 2) ? 25 : 75));
            var y = (x2 == 0) ? 75 : ((x2 == 1) ? 25 : ((x2 == 2) ? -25 : -75));
            var z = (x3 == 0) ? 75 : ((x3 == 1) ? 25 : ((x3 == 2) ? -25 : -75));

            return new Point3D(x, y, z);
        }

        #endregion

        #region Handlers

        private void UserControl_MouseDown(object sender, MouseButtonEventArgs e)
        {
            _mouseDown = true;

            var pos = Mouse.GetPosition(this);
            _lastMousePosition = new Point(pos.X - viewport.ActualWidth / 2, viewport.ActualHeight / 2 - pos.Y);
        }

        private void UserControl_MouseUp(object sender, MouseButtonEventArgs e)
        {
            _mouseDown = false;
        }

        private void UserControl_MouseLeave(object sender, MouseEventArgs e)
        {
            _mouseDown = false;
        }

        private void UserControl_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            camera.Position = new Point3D(camera.Position.X, camera.Position.Y - e.Delta / 20D, camera.Position.Z);
        }

        private void UserControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (!_mouseDown) return;

            var pos = Mouse.GetPosition(this);
            var actualPos = new Point(pos.X - this.ActualWidth / 2, this.ActualHeight / 2 - pos.Y);
            var dx = actualPos.X - _lastMousePosition.X;
            var dy = actualPos.Y - _lastMousePosition.Y;
            var mouseAngle = 0D;

            if (dx == 0 || dy == 0)
            {
                if (dx == 0 && dy != 0)
                    mouseAngle = Math.Sign(dy) > 0 ? Math.PI / 2 : Math.PI * 1.5;
                else if (dx != 0 && dy == 0)
                    mouseAngle = Math.Sign(dx) > 0 ? 0 : Math.PI;
            }
            else
            {
                mouseAngle = Math.Asin(Math.Abs(dy) / Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)));
                if (dx < 0 && dy > 0) 
                    mouseAngle += Math.PI / 2;
                else if (dx < 0 && dy < 0) 
                    mouseAngle += Math.PI;
                else if (dx > 0 && dy < 0) 
                    mouseAngle += Math.PI * 1.5;
            }

            var axisAngle = mouseAngle + Math.PI / 2;
            var axis = new Vector3D(Math.Cos(axisAngle) * 4, 0, Math.Sin(axisAngle) * 4);
            var rotation = (0.02 * Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2))) * 180 / Math.PI;

            var tgroup = model.Transform as Transform3DGroup;
            var r = new QuaternionRotation3D(new Quaternion(axis, rotation));

            tgroup.Children.Add(new RotateTransform3D(r));

            _lastMousePosition = actualPos;
        }

        #endregion
    }
}
